#!/usr/bin/env python
# -*- coding: utf-8 -*-

'''
Cell is base storage unit, which is made up of CellCache and CellStore.

This module is free software, and you may redistribute it and/or modify
it under the same terms as Python itself, so long as this copyright message
and disclaimer are retained in their original form.

IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF
THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.

THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE.  THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS,
AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
'''

__author__ = 'kuerant < kuerant@gmail.com >'
__version__ = '1.0.0'


import os, os.path

import error
import log

from cellcache import CellCache
from cellstore import CellStore

from const import CELL_FILE_NAME_PREFIX
from const import COLUMN_FOUND,COLUMN_NOT_FOUND,COLUMN_DELETED

#######################################################################

class   Cell(object) :
    '''Cell is basic storage unit, and it is made up of CellCache and CellStore.
    '''

    DEFAULT_CACHE_SIZE_LIMIT = 1048576
    DEFAULT_DIR_MODE = 0750
    DEFAULT_FILE_MODE = 0640

    def __init__(self, dir, cache_size_limit=0) :
        '''create a instance of Cell that will store data and provide query service.
        '''
        self._dir = dir

        self.cache_size_limit = Cell.DEFAULT_CACHE_SIZE_LIMIT
        if cache_size_limit : self.cache_size_limit = cache_size_limit

        self._cache = None
        self._store = None
        self._initialized = False
        ## __init__()

    ###################################################################
    # 
    # PUBLIC METHODS
    #

    def insert(self, row_key, key, data) :
        if not self._initialized : self.__init()

        if not self._cache : self.__loadCache()
        if not self._cache : raise RuntimeError()

        # insert record to CellCache
        self._cache.insert(row_key, key, data)

        # if CellCache is full, dump data into store
        if (self._cache.size >= self.cache_size_limit) :
            self.__compact()
        
        ## insert()

    def erase(self, row_key, column_keys=None, time_before=None) :
        '''erase record by key

        @param row_key row-key of record
        @param column_keys columns-keys of record to erase
        @param time_before all records with <key> would be deleted 
                           if their timestamp is OLDER than <time_before>
        '''
        if not self._initialized : self.__init()

        if not self._cache : self.__loadCache()
        if not self._cache : raise RuntimeError()

        raise NotImplementedError()
        ## erase()

    def findColumn(self, row_key, column_key) :
        '''find record by key, and return data of the first one if found.

        @return (status, data, timestamp)
        status - one of { COLUMN_FOUND, COLUMN_NOT_FOUND, COLUMN_DELETED }
        '''
        if not self._initialized : self.__init()

        if not self._cache : self.__loadCache()
        if not self._cache : raise RuntimeError()

        # first, lookup CellCache
        status,data,timestamp = self._cache.findColumn(row_key, column_key)

        # if NOT-FOUND, lookup CellStore
        if COLUMN_NOT_FOUND == status :
            if not self._store : self.__loadStore()
            if self._store :
                status,data,timestamp = self._store.findColumn(row_key, column_key)

        return (status, data, timestamp)
        ## findColumn()

    def getColumnStatus(self, row_key, column_key) :
        '''query status of column

        @return (status,timestamp)
        status - one of { COLUMN_FOUND, COLUMN_NOT_FOUND, COLUMN_DELETED }
        '''
        if not self._initialized : self.__init()

        if not self._cache : self.__loadCache()
        if not self._cache : raise RuntimeError()

        # first, lookup CellCache
        status,timestamp = self._cache.getColumnStatus(row_key, column_key)

        # if NOT-FOUND, lookup CellStore
        if COLUMN_NOT_FOUND == status :
            if not self._store : self.__loadStore()
            if self._store :
                status,timestamp = self._store.getColumnStatus(row_key, column_key)

        return (status, timestamp)
        ## getColumnStatus()


    ###################################################################
    

    ###################################################################
    # 
    # PRIVATE METHODS
    #

    def __init(self) :
        '''initialize resource before cell operations (INSERT/UPDATE/UPSERT/ERASE)

        create storage directory if it does not exist.
        '''
        if self._initialized : return True

        # create storage directory
        if not (os.path.exists(self._dir) and os.path.isdir(self._dir)) :
            os.makedirs(self._dir, Cell.DEFAULT_DIR_MODE)

        self._initialized = True
        return True
        ## __init()

    def __compact(self) :
        '''compact data from CellCache and CellStore into a new CellStore
        '''
        if not self._cache : return
        if not self._store : self.__loadStore()

        def popCache() :
            # require cache_iter
            if not cache_iter :
                return (None,None)

            try :
                key, data = cache_iter.next()
            except StopIteration :
                key, data = None, None
            return (key,data)
            ## popCache()
        def popStore() :
            # require store_iter
            if not store_iter :
                return (None,None)

            try :
                key, data = store_iter.next()
            except StopIteration :
                key, data = None, None
            return (key,data)
            ## popStore()
        def pushStore(store_sink, key, data) :
            # require store_sink
            store_sink.append(key, data)
            ## pushStore()

        def    isDiscardable(guard_smith, key_smith) :
            if guard_smith is None : return False

            to_discard = False
            if guard_smith.isDeleteRowFlag() :
                to_discard = True
            elif guard_smith.isDeleteColumnFlag() :
                if key_smith.column_family == guard_smith.column_family :
                    to_discard = True
            elif guard_smith.isDeleteCellFlag() :
                if (key_smith.column_family == guard_smith.column_family) and (key_smith.column_qualifier == guard_smith.column_qualifier) :
                    to_discard = True
            elif guard_smith.isInsertFlag() :
                if (key_smith.column_family == guard_smith.column_family) and (key_smith.column_qualifier == guard_smith.column_qualifier) and (key_smith.timestamp < guard_smith.timestamp) :
                    to_discard = True
            
            return to_discard
            ## isDiscardable()

        # STEP. initialize
        # TODO : to make sure that CellStore opened serially
        store_sink = CellStore(self._dir)
        
        cache_iter = iter(self._cache)
        if self._store : 
            store_iter = iter(self._store)
        else :
            store_iter = None

        # STEP. merge data from two sources : Cache and Store
        cache_smith = KeySmith()
        store_smith = KeySmith()
        guard_smith = KeySmith()
        guard_row_key = None
        cache_key,cache_data = popCache()
        store_key,store_data = popStore()
        while guard_row_key or cache_key :
            # when there are data from cache
            if not guard_row_key :
                # when at the begin or at the end , guard_row_key is invalid
                while store_key > cache_key :
                    pushStore(store_sink, store_key, store_data)
                    store_key,store_data = popStore()
                # here, store_key <= cache_key
                if cache_key is None : break
                guard_smith.desemble(cache_key)
                guard_row_key = guard_smith.row_key

                cache_smith.desemble(cache_key)
                if cache_smith.isInsertFlag() :
                    pushStore(store_sink, cache_key, cache_data)
                cache_key,cache_data = cache_cells.pop()    
            else :
                if store_key > cache_key :
                    # handle store_key
                    store_smith.desemble(store_key)
                    if guard_row_key == store_smith.row_key :
                        to_discard = isDiscardable(guard_smith, store_smith)
                        if not to_discard :
                            pushStore(store_sink, store_key, store_data)

                        store_key,store_data = popStore()
                    else :
                        guard_row_key = None
                        pushStore(store_sink, store_key, store_data)
                        store_key,store_data = popStore()
                else :
                    # handle cache_key
                    cache_smith.desemble(cache_key)
                    if guard_row_key == cache_smith.row_key :
                        to_discard = isDiscardable(guard_smith, cache_smith)
                        if not to_discard :
                            pushStore(store_sink, cache_key, cache_data)

                        cache_key,cache_data = popCache()
                    else :
                        guard_smith.desemble(cache_key)
                        guard_row_key = guard_smith.row_key

                        cache_smith.desemble(cache_key)
                        if cache_smith.isInsertFlag() :
                            pushStore(store_sink, cache_key, cache_data)
                        cache_key,cache_data = cache_cells.pop()    
                # end -- if store_key > cache_key :
            # end -- if not guard_row_key :
        # end -- while guard_row_key or cache_key :

        while store_key :
            pushStore(store_sink, store_key, store_data) 
            store_key, store_data = popStore()

        # STEP. finalize
        store_sink.freeze()
        store_sink = None
        del store_sink     # SyntaxError: can not delete variable 'store_sink' referenced in nested scope
        store_iter = None
        cache_iter = None
        
        ## __compact()

    def __loadCache(self) :
        '''create a CellCache instance
        '''
        if self._cache : return

        self._cache = CellCache()
        ## __loadCache()

    def __loadStore(self) :
        '''create a CellStore instance, and load the latest store data
        '''
        if self._store : return

        '''
        store_filename = None
        store_revision = 0
        items = os.listdir(self._dir)
        for item in items :
            dir_item = '%s/%s' % (self._dir, item)
            if os.path.isdir(dir_item) : continue
            if not item.startswith(CELL_FILE_NAME_PREFIX) : continue
            _prefix, _timestamp = item.split('.', 1)
            revision = int(_timestamp)
            if revision > store_revision :
                store_revision = revision
                store_filename = item
        '''

        #if store_filename :
        #    store_filename = os.sep.join((self._dir, store_filename))
        #    self._store = CellStore(store_filename)
        self._store = CellStore(self._dir)
        ## __loadStore()
    ## class Cell

